package net.fxug.hokuriku.chocolat.service.sonar
{
    import flash.errors.IOError;
    import flash.events.DatagramSocketDataEvent;
    import flash.events.EventDispatcher;
    import flash.events.TimerEvent;
    import flash.net.DatagramSocket;
    import flash.utils.ByteArray;
    import flash.utils.Timer;
    
    import net.fxug.hokuriku.chocolat.events.SonarEvent;
    import net.fxug.hokuriku.chocolat.model.tracer.Tracer;
    import net.fxug.hokuriku.chocolat.service.sonar.constants.SonarConstants;
    import net.fxug.hokuriku.chocolat.service.sonar.constants.SonarMessageConstants;
    import net.fxug.hokuriku.chocolat.service.sonar.constants.SonarModeConstants;
    import net.fxug.hokuriku.chocolat.service.sonar.data.SocketServerData;
    
    /**
     * サーバーからの返答が来た(通信成功)
     * @eventType net.fxug.hokuriku.chocolat.events.SonarEvent
     */
    [Event(name="serverReplayRecieved", type="net.fxug.hokuriku.chocolat.events.SonarEvent")]

    /**
     * サーバーが見つからなかった
     * @eventType net.fxug.hokuriku.chocolat.events.SonarEvent
     */
    [Event(name="serverNotDiscoverd", type="net.fxug.hokuriku.chocolat.events.SonarEvent")]

    /**
     * ローカル環境内のサーバークライアントを見つける
     * サーバクライアントはずっと待ち受けている必要があるが、そうでない場合は見つけるか、一定時間経過で終了する
     * 
     * [[使い方]]
     * 
     * 【サーバーモード】
     * <pre>
     * // 受信して勝手に返信するだけ
     * var sonar:Sonar = new Sonar(address, SonarModeConstants.SERVER_MODO);
     * sonar.recieve();
     * </pre>
     * 
     * 
     * 【クライアントモード】
     * <pre>
     * // サーバーを探す
     * var sonar:Sonar = new Sonar(IP, SonarModeConstants.CLIENT_MODE);
     * 
     * sonar.addEventListener(SonarEvent.SERVER_REPLY_RECIEVED, this.serverReplyRecievedEventHandler);
     * sonar.addEventListener(SonarEvent.SERVER_NOT_DISCOVER, this.serverNotDiscoverdEventHandler);
     * 
     * sonar.ping(address);
     * </pre>
     * 
     * @author mac
     */
    public class Sonar extends EventDispatcher
    {
        private static var datagramSocket:DatagramSocket;
        private var pingTimer:Timer = new Timer(SonarConstants.SEARCH_LIMITE_TIME, 1);

        /**
        * モード(クライアントモードとサーバーモードがある)
        */
        private var _mode:String;
        
        public function set mode(mode:String):void
        {
            this._mode = mode;
        }
        
        public function get mode():String
        {
            return this._mode;
        }

        /**
         * コンストラクタ
         * 
         * @param inAddress // IPアドレス
         * @param inMode    // モード
         */
        public function Sonar(inAddress:String, inMode:String = SonarModeConstants.UNDEFINE)
        {
			Tracer.t("Sonar::new Sonnar Address = " + inAddress + "/ Port = " + SonarConstants.PORT);
            this._mode = inMode;
            
            if (!Sonar.datagramSocket) {
                Tracer.t("    new Sonar"); 
                Sonar.datagramSocket = new DatagramSocket();
                Sonar.datagramSocket.bind(SonarConstants.PORT);
            }

            Tracer.t("Sonar::Sonar bind local port = " + Sonar.datagramSocket.localPort + " / local address = " + Sonar.datagramSocket.localAddress + " / Mode = " + this._mode);
        }

        /**
         * ピンを打つ
         * 
         * @param address
         */
        public function ping(address:String):void
        {
            var message:ByteArray = new ByteArray();
            
            message.writeUTFBytes(SonarMessageConstants.SEARCH_SERVER);

            Tracer.t("Sonar::ping ip = " + address + "/ port = " + SonarConstants.PORT);

            this.recieve();

            if (Sonar.datagramSocket.bound)
            {
                try
                {
                    Sonar.datagramSocket.send(message, 0, 0, address, SonarConstants.PORT);
                }
                catch (e:Error)
                {
                    Tracer.t("    send faild IP = " + address);
                    
                    Sonar.datagramSocket.close();

                    throw new Error("BroadCast Error");
                }
            } else {
                throw new Error("unconnected ping Error");
            }
            
            // 一定時間経過後にpingが受信できなかったらエラーイベントをディスパッチする
            this.pingTimer.addEventListener(TimerEvent.TIMER_COMPLETE, this.pingTimerCompleteEventHandler);
            this.pingTimer.start();
        }

        /**
         * 待ちうけ開始
         */
        public function recieve():void
        {
            Tracer.t("Sonar::recieve 受信開始");

            Sonar.datagramSocket.addEventListener(DatagramSocketDataEvent.DATA, this.dataReceivedEventHandler);
            Sonar.datagramSocket.receive();
        }

        /**
         * バインドが成功しているかどうか
         * 
         * @return
         */
        public function isBound():Boolean
        {
            if (!Sonar.datagramSocket)
            {
                return false;
            }
            else if (!Sonar.datagramSocket.bound)
            {
                return false;
            }
            return true;
        }
        
        /**
         * データを受信
         */
        private function dataReceivedEventHandler(event:DatagramSocketDataEvent):void
        {
            Tracer.t("Sonar::dataReceivedEventHandler 受信");

            var message:String = event.data.readUTFBytes(event.data.bytesAvailable);

            switch (message)
            {
                // サーバーがクライアントからのサーチを受信した
                case SonarMessageConstants.SEARCH_SERVER:
                    if (this._mode == SonarModeConstants.SERVER_MODE)
                    {
                        Tracer.t("    Received SEARCH_SERVE from " + event.srcAddress + ":" + event.srcPort + "> " + message);

                        var reply_message:ByteArray = new ByteArray();
                        reply_message.writeUTFBytes(SonarMessageConstants.REPLY_SERVER);
                        
                        // クライアントに返答
                        Sonar.datagramSocket.send(reply_message, 0, 0, event.srcAddress, event.srcPort);
                    }
                    else
                    {
                        Tracer.t("    not replay mode = " + this._mode);
                    }
                    break;

                // クライアントがサーバーからの返事を受信した
                case SonarMessageConstants.REPLY_SERVER:
                    Tracer.t("    Received REPLY_SERVER from " + event.srcAddress + ":" + event.srcPort + "> " + message);
                    Tracer.t("        " +event.dstAddress);
                    Tracer.t("        " +event.srcAddress);
                    
                    // サーバーの存在を確認できたのでサーバー情報をディスパッチ
                    var data:SocketServerData = new SocketServerData(event.srcAddress, event.srcPort);

                    var serverReplyReceiveEvent:SonarEvent = new SonarEvent(SonarEvent.SERVER_REPLY_RECIEVED);
                    serverReplyReceiveEvent.serverData = data;

                    this.dispatchEvent(serverReplyReceiveEvent);

                    this.dispose();
                    
                    break;
                default:
                    // TODO エラーハンドリング
                    Tracer.t("    undefined message recieved message = " + message);
                    break;
            }
        }

        /**
         * サーバーが発見できなかった
         */
        private function pingTimerCompleteEventHandler(event:TimerEvent):void
        {
            Tracer.t("Sonar::pingTimerCompleteEventHandler");
            Tracer.t("    サーバーが見つかりませんでした");
            
            var sonar_event:SonarEvent = new SonarEvent(SonarEvent.SERVER_NOT_DISCOVER);
            this.dispatchEvent(sonar_event);
            
            this.dispose();
        }

        /**
         * ディスポーザー
         */
        public function dispose():void
        {
            Tracer.t("Sonar::dispose");
            
            if (this.pingTimer)
            {
                if (Sonar.datagramSocket.hasEventListener(TimerEvent.TIMER_COMPLETE))
                {
                    this.pingTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, this.pingTimerCompleteEventHandler);
                }
                this.pingTimer.stop();
            }
            
            if (Sonar.datagramSocket)
            {
                if (Sonar.datagramSocket.hasEventListener(DatagramSocketDataEvent.DATA))
                {
                    Sonar.datagramSocket.removeEventListener(DatagramSocketDataEvent.DATA, this.dataReceivedEventHandler);
                }
                try {
                    Sonar.datagramSocket.close();
                }
                catch(e:IOError)
                {
                    Tracer.t("    datagramSocket.close ERROR");
                }
            }
        }
    }
}